#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib3
urllib3.disable_warnings()


import json as js
import time
import functools
import paramiko
from json import decoder, dumps
from typing import Any, Callable, Dict, Optional, Union
import requests
import warnings

from requests.adapters import HTTPAdapter
from jsonpath import jsonpath
from urllib.parse import urlencode

# from commons.jmespath import jmespath as jsonpath
from utils.config_tools import Config
from commons.logger import log

IMG = ['jpg', 'jpeg', 'gif', 'bmp', 'webp', 'xlsx', 'png']


warnings.filterwarnings("ignore")


def request(func: Callable) -> Callable:
    """
    装饰器函数，用于包装HTTP请求相关的函数，添加请求和响应的日志记录、处理响应数据等功能。
    参数：
    - func: 被装饰的函数，应该是用于发起 HTTP 请求的函数，期望接收相关的请求参数（如路径、参数、请求头等）并返回 requests.Response 对象。
    返回值：返回包装后的函数，该函数在执行原请求函数的基础上，会添加日志记录和响应数据处理等功能。
    """
    @functools.wraps(func)
    def wrapper(self,*args: tuple, **kwargs: Dict):
        func_name = func.__name__
        log.info('-------------- 开始发送Request请求 -----------------[🚀]')
        # 获取配置文件名称参数，确保传入正确的配置文件名称
        config_name = self.config_name
        if config_name is None:
            log.error("配置文件名称参数不能为空，请确保传入正确的配置文件名称。")
            raise ValueError("配置文件名称参数不能为空")
        # 获取path参数，优先从位置参数中获取，若不存在则从关键字参数中获取，设置合理默认值
        path: str = kwargs.get('path', '') if not args else args[0]
        if not path:
            # 更友好的错误提示，说明path参数的要求
            log.error("path参数不能为空，请确保传入正确的请求路径参数，格式应为符合URL规范的字符串。")
            raise ValueError("path参数不能为空")
        http_request = self
        url = path
        if 'http' not in url:
            url = http_request.base_url + path

        # 判断是否为图片文件类型（优化为更严谨的判断方式，结合Content-Type和文件后缀名等多方式判断，示例简单示意）
        img_file: bool = False
        # content_type: str = requests.head(url).headers.get('Content-Type', '')
        # file_extension = get_file_extension(url)  # 获取文件后缀名（示例函数，需实现）
        # if content_type.startswith('image/') or is_image_extension(file_extension):  # 判断后缀名是否为图片后缀（示例函数，需实现）
        #     img_file = True

        log.info(f'[method]: {func_name.upper()}     [url]: {url} \n')

        # 设置可选参数的默认值，使代码更清晰
        auth: Optional[Dict] = kwargs.get('auth', None)
        headers: Optional[Dict] = kwargs.get('headers', None)
        cookies: Optional[Dict] = kwargs.get('cookies', None)
        params: Optional[Dict] = kwargs.get('params', None)
        data: Optional[Union[Dict, str]] = kwargs.get('data', None)
        json_data: Optional[Dict] = kwargs.get('json', None)

        # 记录请求发起的时间戳，用于计算请求总耗时
        request_start_time = time.time()
        # 记录请求参数的日志信息（提取到单独函数可提高可读性和可维护性）
        _log_request_parameters(auth, headers, cookies, params, data, json_data, func_name)

        # 发送http请求
        try:
            r = func(self, *args, **kwargs)
        except requests.RequestException as e:
            log.error(f"请求 {func_name} 出现异常，异常类型: {e.__class__.__name__}，异常信息: {str(e)}")
            # 可根据具体情况进行重试等处理，这里简单重新抛出异常
            raise

        ResponseResult.status_code = r.status_code
        log.info('-------------- 开始处理Response ----------------[🛬️]')
        if ResponseResult.status_code == 200 or ResponseResult.status_code == 304:
            log.info(
                f'successful with status {str(ResponseResult.status_code)} \n')
            # 如果响应是JSON格式，记录一些关键信息（示例，假设响应中有"data"字段比较重要）
            try:
                response_data = r.json()
                if "data" in response_data:
                    log.info(f"响应中的关键信息（示例）: [data: {response_data['data']}]")
            except requests.exceptions.JSONDecodeError:
                log.warning("响应数据无法解析为JSON格式，无法获取关键信息。")
        else:
            log.warning(
                f'unsuccessful with status {str(ResponseResult.status_code)} \n')
            # 记录服务器返回的错误信息（如果有），方便排查问题
            if r.text:
                log.warning(f"服务器返回的错误信息（示例）: {r.text}")

        resp_time = r.elapsed.total_seconds()
        request_total_time = time.time() - request_start_time  # 计算请求总耗时
        log.info(f"请求总耗时: {request_total_time} 秒，响应耗时: {resp_time} 秒")

        # 处理响应数据，提取到单独函数增强可读性和可维护性
        _handle_response_data(r, img_file, resp_time)

        return r

    return wrapper

def _log_request_parameters(auth: Optional[Dict], headers: Optional[Dict],
                            cookies: Optional[Dict], params: Optional[Dict],
                            data: Optional[Union[Dict, str]],
                            json_data: Optional[Dict],
                            request_identifier: str = "") -> None:
    """
    记录请求相关的各种参数信息到日志中，根据参数是否存在及类型进行合理的日志记录。

    参数:
    - auth: 可选的认证相关信息字典，可能包含如用户名、密码等用于请求认证的数据。
    - headers: 可选的请求头信息字典，包含如Content-Type、User-Agent等请求相关的配置信息。
    - cookies: 可选的Cookies信息字典，用于传递客户端与服务器之间的会话相关标识等信息。
    - params: 可选的请求参数字典，通常用于GET请求等携带查询参数。
    - data: 可选的数据信息，可以是字典类型（一般用于POST等请求提交的数据）或者字符串类型（例如特定格式的文本数据）。
    - json_data: 可选的JSON格式数据字典，用于明确以JSON格式提交的数据（和data有区别，取决于接口要求）。
    - request_identifier: 用于标识请求的字符串，比如请求的接口名称或者URL的关键部分等，方便在日志中区分不同请求的参数信息。
    """
    # 记录认证信息（auth），注意对敏感信息进行脱敏处理（示例简单将值替换为******，实际需根据具体情况合理脱敏）
    if auth:
        desensitized_auth = {}
        for key, value in auth.items():
            if "password" in key.lower():  # 假设包含password字段为敏感信息，进行脱敏
                desensitized_auth[key] = "******"
            else:
                desensitized_auth[key] = value
        log.debug(f'请求参数auth: {js.dumps(desensitized_auth, ensure_ascii=False, indent=2)} (已对敏感信息脱敏)， [请求标识: {request_identifier}]')

    # 记录请求头信息（headers），部分关键的headers信息可考虑用INFO级别记录（示例判断Content-Type，可按需调整）
    if headers:
        if type(headers) == dict:
            content_type = headers.get("Content-Type", "")
            if content_type:
                log.info(f'请求参数headers: Content-Type: {content_type} (关键请求头信息)，[请求标识: {request_identifier}]')
        log.debug(f'请求参数headers: {js.dumps(headers, ensure_ascii=False, indent=2)} (完整请求头信息)，[请求标识: {request_identifier}]')

    # 记录Cookies信息
    if cookies:
        log.debug(f'请求参数cookies: {js.dumps(cookies, ensure_ascii=False, indent=2)}，[请求标识: {request_identifier}]')

    # 记录请求参数（params），封装json序列化逻辑到单独函数提高复用性（这里简单内联展示）
    if params:
        req_params = _serialize_and_log_data(params, "params", request_identifier)
        log.debug(f'请求参数params: {req_params},请求标识: {request_identifier}')

    # 记录data信息，对可能出现的TypeError异常进行更详细的处理和日志记录
    if data:
        try:
            req_data = _serialize_and_log_data(data, "data", request_identifier)
            log.debug(f'请求参数data: {req_data},请求标识: {request_identifier}')
        except TypeError as e:
            log.error(f'无法序列化data参数，原始数据: {data}，错误信息：{e}，[请求标识: {request_identifier}]')

    # 记录json_data信息
    if json_data:
        req_json = _serialize_and_log_data(json_data, "json_data", request_identifier)
        log.debug(f'请求参数json: {req_json},请求标识: {request_identifier}')


def _serialize_and_log_data(data, parameter_name, request_identifier):
    """
    对给定的数据进行JSON序列化，并记录相关日志信息。

    参数:
    - data: 需要序列化的数据，可以是字典等可序列化类型。
    - parameter_name: 参数名称，用于在日志中标识是哪个参数的数据。
    - request_identifier: 请求的标识信息，用于区分不同请求的日志记录。

    返回:
    - 序列化后的字符串数据，如果序列化成功。
    """
    try:
        serialized_data = js.dumps(
            data,
            ensure_ascii=False,
            sort_keys=True,
            indent=2,
        )
        # log.debug(f'[{parameter_name}: {serialized_data}] [请求标识: {request_identifier}]')
        return serialized_data
    except TypeError as e:
        log.error(f'[{parameter_name}: 无法序列化该参数数据，出现TypeError异常，原因: {e}] [请求标识: {request_identifier}]')
        raise

def _handle_response_data(r: requests.Response, img_file: bool,
                          resp_time: float) -> None:
    """
    根据响应的不同情况（如JSON格式、文本、图片等）进行相应的处理，并记录相关信息到日志中。

    参数:
    - r: requests.Response对象，包含了服务器返回的响应信息。
    - img_file: 布尔值，表示响应是否为图片类型，通过一定的判断逻辑预先确定。
    - resp_time: 本次请求响应所花费的时间，单位为秒。

    该函数主要职责是对响应数据进行合理处理，根据其类型（JSON、文本、图片）分别进行不同的操作，
    包括解析JSON数据、记录相应的日志信息以及将处理后的响应数据存储到ResponseResult类的response属性中。
    """
    try:
        # 尝试将响应解析为JSON格式数据
        parsed_json_response = r.json()
        log.debug(f'解析数据为json格式花费时间: {resp_time} s')
        # 将解析后的JSON数据格式化输出并记录日志，方便查看详细结构
        resp_json = js.dumps(
            parsed_json_response,
            ensure_ascii=False,
            sort_keys=True,
            indent=2,
        )
        log.debug(f'解析后的JSON数据为：\n {resp_json} \n')

        # 将解析后的JSON数据存储到ResponseResult.response中，方便后续使用
        ResponseResult.response = parsed_json_response
    except requests.exceptions.JSONDecodeError as msg:
        log.warning(f'响应结果转换为json失败，错误信息: {msg}]')
        if img_file:
            content_type = r.headers.get("Content-Type", "")
            log.debug(f'响应结果类型: {content_type}，花费时间: {resp_time} s')
            # 对于图片类型响应，直接将响应内容（字节数据）存储到ResponseResult.response中
            # 这里可以添加一些额外验证逻辑，比如检查图片数据是否完整（示例）
            if len(r.content) > 0:
                ResponseResult.response = r.content
            else:
                log.error(f'收到的响应结果图片信息为空')
        else:
            log.debug(f'响应结果类型: text，花费时间: {resp_time} s\n')
            text_response = r.text
            log.debug(f'响应结果Text文本:\n {text_response} \n')
            # 对于文本类型响应，添加文本长度信息到日志记录中（示例）
            log.info(f'Text文本长度: {len(text_response)} characters]')
            # 将文本数据存储到ResponseResult.response中
            ResponseResult.response = text_response
    except requests.RequestException as req_error:
        log.error(f'[Error: Request Exception Occurred While Handling Response. Error: {req_error}]')



def get_file_extension(url: str) -> str:
    """
    获取URL对应的文件后缀名，示例简单从URL最后一个"."后截取，可根据实际情况完善。

    参数:
    - url: 请求的URL字符串。

    返回值:
    - 文件后缀名，如果存在的话，否则返回空字符串。
    """
    parts = url.split('.')
    if len(parts) > 1:
        return parts[-1]
    return ""


def is_image_extension(extension: str) -> bool:
    """
    判断给定的文件后缀名是否为常见的图片后缀名，示例简单列举常见的几种，可根据实际情况扩充。

    参数:
    - extension: 文件后缀名字符串。

    返回值:
    - 如果是常见图片后缀名，返回True，否则返回False。
    """
    image_extensions = ["jpg", "jpeg", "png", "gif", "bmp"]
    return extension.lower() in image_extensions

class ResponseResult:
    status_code = 200
    response = None


class HttpRequest:
    _cached_base_url = None  # 用于缓存基础URL，避免每次请求都重新读取配置文件
    session = requests.Session()  # 使用requests.Session()来复用连接，提高性能
    connect_adapter = HTTPAdapter(max_retries=3)
    session.mount("https://", connect_adapter)
    session.mount("http://", connect_adapter)
    def __init__(self, config_name, u_token=None):
        """
        初始化HttpRequest对象，接收配置文件名称以及可选的token参数。
        根据传入的配置文件名称初始化Config类来获取基础URL等配置信息，
        并可以传入一个token用于后续请求中自动添加到URL中（如果有需要）。

        参数:
        :param config_name: 配置文件名称，用于初始化Config类来读取相关配置。
        :param u_token: 可选参数，用于后续请求中添加到URL的token字符串（默认为None）。
        """
        self.config_name = config_name
        self.token = u_token
        self._init_base_url()  # 调用方法来初始化基础URL

    def _init_base_url(self):
        """
        私有方法，用于初始化基础URL。
        通过Config类读取配置文件获取基础URL，添加错误处理及考虑性能优化（示例简单处理，可完善缓存机制等）。
        """
        try:
            # if not self._cached_base_url:
            #     config = Config(self.config_name)  # 根据传入的配置文件名称初始化Config类
            #     self._cached_base_url = config.read_config_ini('envHost', 'HOST')
            #     log.info(f"成功从配置文件读取基础URL: {self._cached_base_url}")
            #     self.base_url = self._cached_base_url  # 将获取到的基础URL赋值给实际使用的base_url属性
            config = Config(self.config_name)
            self.base_url = config.read_config_ini('envHost', 'HOST')
        except FileNotFoundError:
            log.error("配置文件不存在，请检查配置文件路径是否正确")
            raise
        except KeyError:
            log.error("配置文件中对应节点不存在，请检查配置文件中相关节点配置是否正确")
            raise
        except Exception as e:
            log.error(f"读取配置文件获取基础URL时出现其他错误，错误信息: {e}")
            raise

    def _build_url(self, path):
        """
        私有方法，用于根据传入的路径构建完整的请求URL。
        如果路径本身不包含协议（http或https），则拼接上类中配置的基础URL，并根据是否有token添加相应的查询参数。

        参数:
        - path: 请求的路径字符串，通常是接口的相对路径部分。

        返回值:
        - 构建好的完整请求URL字符串。
        """
        url = path
        token_query = "" if not self.token else "?token=" + self.token
        if 'http' not in url:
            url = self.base_url + path + token_query
        else:
            url = path + token_query
        return url

    def update_base_url(self):
        """
        公共方法，用于重新读取配置文件并更新 base_url。
        """
        try:
            config = Config(self.config_name)
            new_base_url = config.read_config_ini('envHost', 'HOST')
            self.base_url = new_base_url
            log.info(f"成功更新基础URL为: {self.base_url}")
        except FileNotFoundError:
            log.error("配置文件不存在，请检查配置文件路径是否正确")
        except KeyError:
            log.error("配置文件中对应节点不存在，请检查配置文件中相关节点配置是否正确")
        except Exception as e:
            log.error(f"更新基础URL时出现其他错误，错误信息: {e}")

    def _validate_params(self, params):
        """
        简单验证params参数是否符合要求（示例，可完善更严格的校验）。
        判断参数是否为可迭代类型（字典、列表、元组），可进一步完善验证逻辑。

        参数:
        :param params: 请求参数字典。

        返回值:
        :return: 如果参数合法返回True，否则返回False。
        """
        return isinstance(params, (dict, list, tuple))  # 简单判断是否为可迭代类型，可进一步完善

    def _validate_json(self, json_data):
        """
        简单验证json参数是否可序列化（示例，可使用更严格的JSON校验库等完善）。
        尝试使用json.dumps将数据转换为JSON字符串，若成功则表示可序列化，返回True，否则返回False。

        参数:
        :param json_data: JSON格式数据字典。

        返回值:
        :return: 如果可序列化返回True，否则返回False。
        """
        try:
            js.dumps(json_data)
            return True
        except:
            return False

    @request
    def get(self, path, params=None, headers=None, **kwargs):
        """
        发起GET类型的HTTP请求。

        参数:
        - path: 请求的路径字符串，相对路径或者包含完整协议的URL。
        - params: 可选的请求参数字典，用于GET请求中传递查询参数，默认为None。
        - headers: 可选的请求头信息字典，用于设置请求的头部信息，默认为None。
        - **kwargs: 其他可选的关键字参数，可用于传递如认证信息等额外的请求相关参数。

        返回值:
        - requests.Response对象，包含服务器返回的响应信息。

        日志记录:
        - 在发起请求前记录请求的相关参数信息（包括路径、参数、请求头等），请求完成后记录响应的状态码以及基本的成功/失败信息，
        - 针对GET请求，额外记录请求的参数情况（如果有），方便排查问题时了解具体的请求内容。
        """
        url = self._build_url(path)
        log.info(f"发起GET请求，请求URL: {url}")
        if params:
            log.debug(f"GET请求携带的参数: {params}")
        try:
            response = self.session.get(url, params=params, headers=headers,verify=False, **kwargs)
            return response
        except requests.exceptions.SSLError as ssl_err:
            log.error(f"SSL错误: {str(ssl_err)}")
            raise
        except requests.RequestException as e:
            log.error(f"GET请求出现异常，异常类型: {e.__class__.__name__}，异常信息: {str(e)}。该异常可能导致无法获取到期望的数据，请检查网络连接、接口是否可用等情况。")
            raise

    @request
    def post(self, path, data=None, json=None, params=None, headers=None,verify=False, **kwargs):
        """
        发起POST类型的HTTP请求。

        参数:
        - path: 请求的路径字符串，相对路径或者包含完整协议的URL。
        - data: 可选的数据信息，可以是字典类型（一般用于表单提交的数据）或者字符串类型（例如特定格式的文本数据），默认为None。
        - json: 可选的JSON格式数据字典，用于明确以JSON格式提交的数据（和data有区别，取决于接口要求），默认为None。
        - params: 可选的请求参数字典，用于GET请求中传递查询参数，默认为None。
        - headers: 可选的请求头信息字典，用于设置请求的头部信息，默认为None。
        - **kwargs: 其他可选的关键字参数，可用于传递如认证信息等额外的请求相关参数。

        返回值:
        - requests.Response对象，包含服务器返回的响应信息。

        日志记录:
        - 在发起请求前记录请求的相关参数信息（包括路径、参数、请求头等），请求完成后记录响应的状态码以及基本的成功/失败信息，
        - 针对POST请求，额外记录提交的数据情况（长度、类型等大致信息，如果合适的话），便于排查问题时知晓具体的请求内容发送情况。
        """
        url = self._build_url(path)
        log.info(f"发起POST请求，请求URL: {url}")
        if data:
            log.debug(f"POST请求提交的数据（data参数）: 类型为 {type(data)}，长度为 {len(data)}")
        if json:
            log.debug(f"POST请求提交的JSON数据（json参数）: 包含 {len(json)} 个字段（示例，可完善更详细的结构信息）")
        try:
            # print(url)
            # print(params)
            # print(headers)
            response = self.session.post(url, params=params, data=data, json=json, headers=headers,verify=verify, **kwargs)
            # print(response.headers.get('X-Rsa-Token'))  -->没有用token直接拿到了公钥
            # print(response.headers)
            return response
        except requests.RequestException as e:
            log.error(f"POST请求出现异常，异常类型: {e.__class__.__name__}，异常信息: {str(e)}。可能导致数据无法成功提交到服务器，请检查数据格式、接口要求等是否正确。")
            raise

    @request
    def post2(self, path, data=None, json=None, params=None, headers=None, verify=False, **kwargs):
        """
        发起POST类型的HTTP请求。

        参数:
        - path: 请求的路径字符串，相对路径或者包含完整协议的URL。
        - data: 可选的数据信息，可以是字典类型（一般用于表单提交的数据）或者字符串类型（例如特定格式的文本数据），默认为None。
        - json: 可选的JSON格式数据字典，用于明确以JSON格式提交的数据（和data有区别，取决于接口要求），默认为None。
        - params: 可选的请求参数字典，用于GET请求中传递查询参数，默认为None。
        - headers: 可选的请求头信息字典，用于设置请求的头部信息，默认为None。
        - **kwargs: 其他可选的关键字参数，可用于传递如认证信息等额外的请求相关参数。

        返回值:
        - requests.Response对象，包含服务器返回的响应信息。

        日志记录:
        - 在发起请求前记录请求的相关参数信息（包括路径、参数、请求头等），请求完成后记录响应的状态码以及基本的成功/失败信息，
        - 针对POST请求，额外记录提交的数据情况（长度、类型等大致信息，如果合适的话），便于排查问题时知晓具体的请求内容发送情况。
        """
        url = self._build_url(path)
        log.info(f"发起POST请求，请求URL: {url}")

        # 处理data参数（表单数据情况）
        if data:
            if isinstance(data, dict):
                # 对字典类型的data进行编码，确保包含中文等字符能正确传输
                data = urlencode(data, encoding='utf-8')
            log.debug(f"POST请求提交的数据（data参数）: 类型为 {type(data)}，长度为 {len(data)}")

        # 处理json参数（JSON数据情况）
        if json:
            try:
                # 将JSON数据转换为字符串，确保中文字符正常编码，指定编码为UTF-8
                json_str = js.dumps(json, ensure_ascii=False)
                # json_str = urlencode(json, encoding='utf-8')
                log.debug(f"POST请求提交的JSON数据（json参数）: 包含 {len(json)} 个字段（示例，可完善更详细的结构信息）")
                json = json_str
            except TypeError as te:
                log.error(f"将JSON数据转换为字符串时出错，错误信息: {str(te)}，请检查JSON数据格式是否正确。")
                raise

        # 设置请求头中编码相关字段，确保和数据编码匹配以及告知服务器期望的编码格式
        if headers is None:
            headers = {}
        headers[
            'Content-Type'] = 'application/json; charset=utf-8' if json else 'application/x-www-form-urlencoded; charset=utf-8'
        headers['Accept-Charset'] = 'utf-8'

        try:
            response = self.session.post(url, params=params, data=data, json=json, headers=headers, verify=verify,
                                         **kwargs)
            return response
        except requests.RequestException as e:
            log.error(f"POST请求出现异常，异常类型: {e.__class__.__name__}，异常信息: {str(e)}。可能导致数据无法成功提交到服务器，请检查数据格式、接口要求等是否正确。")
            raise

    @request
    def delete(self, path, data=None, params=None, headers=None, **kwargs):
        """
        发起DELETE类型的HTTP请求。

        参数:
        - path: 请求的路径字符串，相对路径或者包含完整协议的URL。
        - data: 可选的数据信息，可以是字典类型（一般用于携带删除相关的数据，根据接口要求）或者字符串类型（例如特定格式的文本数据），默认为None。
        - params: 可选的请求参数字典，用于GET请求中传递查询参数，默认为None。
        - headers: 可选的请求头信息字典，用于设置请求的头部信息，默认为None。
        - **kwargs: 其他可选的关键字参数，可用于传递如认证信息等额外的请求相关参数。

        返回值:
        - requests.Response对象，包含服务器返回的响应信息。

        日志记录:
        - 在发起请求前记录请求的相关参数信息（包括路径、参数、请求头等），请求完成后记录响应的状态码以及基本的成功/失败信息，
        - 针对DELETE请求，记录要删除的数据相关信息（如果有），帮助排查问题时了解请求的意图和具体操作内容。
        """
        url = self._build_url(path)
        log.info(f"发起DELETE请求，请求URL: {url}")
        if data:
            log.debug(f"DELETE请求携带的数据（data参数）: 类型为 {type(data)}")
        try:
            response = self.session.delete(url, data=data, params=params, headers=headers, verify=False ,**kwargs)
            return response
        except requests.RequestException as e:
            log.error(f"DELETE请求出现异常，异常类型: {e.__class__.__name__}，异常信息: {str(e)}。可能导致删除操作无法正常执行，请检查相关参数、接口是否支持删除等情况。")
            raise

    @property
    def response(self) -> dict:
        """
        获取接口请求结果属性。

        返回值:
        - 返回接口请求的结果数据，通常是字典类型（根据业务实际情况可能为其他类型）。

        日志记录:
        - 在返回结果时记录结果的基本情况，如类型、是否为空等信息，方便使用者了解获取到的数据特征。
        """
        result = ResponseResult.response
        result_type = type(result).__name__
        log.info(f"获取接口请求结果，结果类型为: {result_type}")
        if not result:
            log.warning("接口请求结果为空，请检查请求是否成功以及服务器是否返回了有效数据。")
        return result

def check_response(describe: str = '',
                   status_code: int = 200,
                   ext: any = None,
                   check: any = None,
                   debug: bool = False):
    """
    检查接口响应数据的装饰器函数。

    参数:
    - describe: 用于描述被装饰函数对应的接口操作，方便在日志中清晰展示接口相关的业务含义，默认为空字符串。
    - status_code: 期望接口返回的HTTP状态码，用于与实际返回的状态码进行对比验证，默认值为200，应该传入合法的HTTP状态码范围（100 - 599）内的值。
    - ext: 可以是提取接口返回字段的表达式（字符串形式，用于 `jsonpath` 函数提取数据）、字典类型（包含提取表达式和期望的值用于验证）或者可调用对象（返回提取表达式或字典等符合要求的数据），用于获取和验证接口响应中的特定数据，默认为None。
    - check: 类似 `ext`，用于检查接口返回的字段，通过指定的表达式提取数据并与期望的值进行断言验证，可接受可调用对象、字典类型，默认为None。
    - debug: 布尔值，用于控制是否开启详细的调试信息打印，开启后会记录更多如函数参数、完整响应内容等信息，默认为False。

    示例：
    @check_response(check = {"$..system_version": "1.0.0.1917"},ext="$..token")
    @check_response(check={"$..system_version": "1.0.0.1917","$..version_number": 100001917}, ext="$..token,$..uglink")

    返回值:
    - 返回一个装饰器，该装饰器可用于包装接口请求相关的函数，在函数执行后对接口响应进行一系列检查并记录相应的日志信息。
    """

    def decorator(func):  # func = decorator(func)
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            func_name = func.__name__
            if debug is True:
                log.debug(f'Execute {func_name} - args: {args}')
                log.debug(f'Execute {func_name} - kwargs: {kwargs}')

            # 执行函数
            r = func(*args, **kwargs)

            try:
                response_json = r.json()
                log.debug(f'成功将响应解析为JSON格式，解析后的数据: {js.dumps(response_json, indent=2)}')
                # 检查是否为 token 失效的情况
                if response_json.get('code') == 1024 and 'Login has expired' in response_json.get('msg', ''):
                    log.warning(f'{func_name} - {describe} 请求时，token 失效，直接返回响应数据')
                    return response_json
            except js.JSONDecodeError as e:
                log.error(
                    f'Execute {func_name} - {describe} failed: Not in JSON format，响应原始内容: {r.text}'
                )
                raise Exception(
                    f'Execute {func_name} - {describe} failed: Not in JSON format，响应原始内容: {r.text}'
                ) from e

            # 验证状态码是否在合法的HTTP状态码范围内
            if not (100 <= status_code <= 599):
                log.error(f'传入的状态码 {status_code} 不在合法的HTTP状态码范围内，请检查参数传入是否正确。')
                raise ValueError('状态码参数超出合法范围')
            assert r.status_code == status_code, f'{func_name}请求时, 状态码不等于预期值: {r.status_code}!= {status_code}，实际返回状态码 {r.status_code}，预期状态码 {status_code}'

            if debug is True:
                log.debug(f'Execute {func_name} - response:\n {js.dumps(response_json, indent=2)}')

            log.info(f'Execute {func_name} - {describe} success!')

            # 处理check参数相关逻辑，提取到独立函数提高可读性（这里简单内联展示）
            _handle_check(func_name, describe, response_json, check)

            # 处理ext参数相关逻辑，提取到独立函数提高可读性（这里简单内联展示）
            return_data = _handle_ext(func_name, describe, response_json, ext)
            return return_data

        return wrapper

    return decorator


def _handle_check(func_name: str, describe: str, response_json: dict, check: Any):
    """
    处理检查接口返回字段的逻辑，根据 `check` 参数的类型（可调用对象或字典）进行相应的数据提取和断言验证。

    参数:
    - func_name: 被装饰函数的名称，用于在日志和错误信息中标识具体的函数。
    - describe: 接口操作描述，用于在日志中展示更详细的业务含义。
    - response_json: 已经解析为字典的接口响应数据，从中提取要检查的字段数据。
    - check: 可以是可调用对象（返回包含提取表达式和期望的值的字典）或者直接是字典类型（包含提取表达式和期望的值用于验证），用于指定要检查的接口返回字段及对应期望的值。
    """
    if hasattr(check, '__call__'):
        check_result = check()
        if check_result:
            for expr, value in check_result.items():
                data = jsonpath(response_json,expr)[0]
                log.debug(f'使用表达式 {expr} 从响应数据中提取检查字段，提取结果: {data}')
                try:
                    assert data == value
                except AssertionError as e:
                    log.error(
                        f'{func_name} - {describe} 请求时，检查接口返回字段出现错误，响应体数据不符合预期值:{data}!= {value}，期望的值: {value}，实际响应数据: {response_json}'
                    )
                    raise e
        else:
            log.warning(f'{func_name} - {describe} 的check可调用对象返回为空，跳过检查接口返回字段步骤。')
    else:
        if check is not None:
            for expr, value in check.items():
                data = jsonpath(response_json,expr)[0]
                log.debug(f'使用表达式 {expr} 从响应数据中提取检查字段，提取结果: {data}')
                try:
                    assert data == value
                except AssertionError as e:
                    log.error(
                        f'{func_name} - {describe} 请求时，检查接口返回字段出现错误，响应体数据不符合预期值:{data}!= {value}，期望的值: {value}，实际响应数据: {response_json}'
                    )
                    raise e
        else:
            log.warning(f'{func_name} - {describe} 的check参数为None，跳过检查接口返回字段步骤。')



def _handle_ext(func_name: str, describe: str, response_json: Dict, ext: Union[Callable, Dict, str, None]):
    """
    处理提取接口返回字段并返回相关数据的逻辑，根据 `ext` 参数的类型（可调用对象或字典等）进行相应的数据提取和返回处理，同时进行必要的错误日志记录。
    支持 `ext` 参数为单个表达式或多个以逗号分隔的表达式，能正确提取并返回对应的数据。

    参数:
    - func_name: 被装饰函数的名称，用于在日志和错误信息中标识具体的函数。
    - describe: 接口操作描述，用于在日志中展示更详细的业务含义。
    - response_json: 已经解析为字典的接口响应数据，从中提取要返回的字段数据。
    - ext: 可以是可调用对象（返回提取表达式用于获取数据）、字典类型（包含提取表达式和期望的值，这里仅取表达式提取数据）或者直接是提取表达式（字符串形式），用于指定要提取的接口返回字段数据，也可以是多个表达式以逗号分隔的字符串形式。
    """
    if hasattr(ext, '__call__'):
        ext_expr = ext()
        if ext_expr:
            expressions = ext_expr if isinstance(ext_expr, list) else [ext_expr]
            result_data = []
            for expr in expressions:
                data_list = jsonpath(response_json, expr)
                if data_list:
                    result_data.extend(data_list)
                    log.debug(f'使用可调用对象 {ext} 返回的表达式 {expr} 从响应数据中提取返回字段，提取结果: {data_list}')
                else:
                    log.debug(f'使用可调用对象 {ext} 返回的表达式 {expr} 从响应数据中提取返回字段，未提取到数据')
            if not result_data:
                log.error(f'Execute {func_name} - return {ext()} is None，响应数据: {response_json}')
            return result_data
        else:
            return response_json
    else:
        if ext is not None:
            expressions = ext.split(',') if isinstance(ext, str) else [ext]
            result_data = []
            for expr in expressions:
                data_list = jsonpath(response_json, expr.strip())  # 去除表达式两端可能的空格
                if data_list:
                    result_data.extend(data_list)
                    log.debug(f'使用表达式 {expr} 从响应数据中提取返回字段，提取结果列表为: {data_list}')
                else:
                    log.debug(f'使用表达式 {expr} 从响应数据中提取返回字段，未提取到数据')
            if not result_data:
                log.error(f'Execute {func_name} - return {ext} is None，响应数据: {response_json}')
            return result_data[0]
        else:
            return response_json


def retry_func(max_retries=3, retry_interval=10):
    """
    重试装饰器，用于为函数添加重试功能。

    参数:
    - max_retries: 最大重试次数，默认为3，需传入大于等于0的整数。
    - retry_interval: 重试间隔时间（秒），默认为10秒，需传入大于0的数值。

    该装饰器会捕获函数执行过程中出现的 requests 相关异常、paramiko 的 SSH 异常以及其他通用异常，
    并根据设定的重试次数和间隔时间进行重试操作，同时详细记录每次重试的相关日志信息。
    """
    if not isinstance(max_retries, int) or max_retries < 0:
        log.error("参数 max_retries 必须是大于等于0的整数，请检查传入参数")
        raise ValueError("参数 max_retries 不符合要求")
    if not isinstance(retry_interval, (int, float)) or retry_interval <= 0:
        log.error("参数 retry_interval 必须是大于0的数值，请检查传入参数")
        raise ValueError("参数 retry_interval 不符合要求")

    def decorator(func):
        def wrapper(*args, **kwargs):
            total_attempts = 0  # 记录函数总的执行尝试次数（包括重试次数）
            for retry_count in range(max_retries):
                total_attempts += 1
                try:
                    log.info(f"即将调用函数 {func.__name__}，参数为 {args} 和 {kwargs}")
                    return func(*args, **kwargs)
                except requests.exceptions.Timeout:
                    log.error(f"请求超时异常发生（第 {total_attempts} 次尝试，第 {retry_count + 1} 次重试），请检查网络连接或服务器响应情况，可能是网络延迟导致的超时")
                except requests.exceptions.ConnectionError:
                    log.error(f"请求连接异常发生（第 {total_attempts} 次尝试，第 {retry_count + 1} 次重试），请检查网络配置以及服务器是否可正常访问")
                except requests.exceptions.RequestException as req_error:
                    log.error(f"其他请求相关异常发生（第 {total_attempts} 次尝试，第 {retry_count + 1} 次重试）：{req_error}")
                except paramiko.ssh_exception.SSHException as ssh_error:
                    log.error(f"SSH相关异常发生（第 {total_attempts} 次尝试，第 {retry_count + 1} 次重试）：{ssh_error}")
                except Exception as e:
                    log.error(f"其他未知异常发生（第 {total_attempts} 次尝试，第 {retry_count + 1} 次重试）：{e}")

                if retry_count < max_retries - 1:
                    log.info(f"第 {retry_count + 1} 次执行失败，等待 {retry_interval} 秒后进行第 {retry_count + 2} 次调用函数{func.__name__}")
                    time.sleep(retry_interval)
                else:
                    log.error(f"已达到最大重试次数 {max_retries}，放弃执行 {func.__name__}")
                    log.error(f"函数 {func.__name__} 经过 {total_attempts} 次尝试后执行失败，具体异常信息可查看前文日志")
                    raise
            return None  # 如果所有重试都失败，返回None（可根据实际需求调整返回值）
        return wrapper
    return decorator


if __name__ == '__main__':
    pass
